package com.hzhh123.crypto.advice;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hzhh123.crypto.annotation.EncryptDecrypt;
import com.hzhh123.crypto.annotation.encrypt.*;
import com.hzhh123.crypto.entity.Result;
import com.hzhh123.crypto.enums.EncrptyTypeEnum;
import com.hzhh123.crypto.exception.EncryptException;
import com.hzhh123.crypto.handle.CryptoContext;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.annotation.Annotation;

/**
 * @author hzhh123
 * @version 1.0
 * @date 2022/4/13 9:22
 * @des 响应结果加密
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<Result<?>> {
    private final ObjectMapper objectMapper;
    private final CryptoContext cryptoContext;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //方法上加密注解
        Annotation[] methodAnnotations = returnType.getMethodAnnotations();
        for (Annotation methodAnnotation : methodAnnotations) {
            if (methodAnnotation instanceof Encrypt
                    || methodAnnotation instanceof RsaEncrypt
                    || methodAnnotation instanceof Base64Encrypt
                    || methodAnnotation instanceof AesEncrypt
                    || methodAnnotation instanceof DesEncrypt
                    || methodAnnotation instanceof EncryptDecrypt) {
                return true;
            }

        }
        //类上有加密注解
        Annotation[] annotations = returnType.getDeclaringClass().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Encrypt
                    || annotation instanceof RsaEncrypt
                    || annotation instanceof Base64Encrypt
                    || annotation instanceof AesEncrypt
                    || annotation instanceof DesEncrypt
                    || annotation instanceof EncryptDecrypt) {
                return true;
            }

        }
        return false;
    }

    @Override
    public Result<?> beforeBodyWrite(Result<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        Object data = body.getData();
        if (data == null) {
            return body;
        }
        String cryptoType = getCryptoType(returnType);
        if (cryptoType == null) {
            throw new EncryptException("Encryption class not registered. (未注册加密方法类)");
        }
        //加密data数据
        String dataText = null;
        try {
            dataText = objectMapper.writeValueAsString(body.getData());
        } catch (JsonProcessingException e) {
            throw new EncryptException("Encrypting data failed. (加密数据失败)");
        }
        String dataStr = cryptoContext.getInstance(cryptoType).encrypt(dataText);
        return Result.builder()
                .code(body.getCode())
                .data(dataStr)
                .message(body.getMessage())
                .success(body.isSuccess())
                .timestamp(body.getTimestamp())
                .build();
    }


    private String getCryptoType(MethodParameter returnType) {
        Annotation[] annotations = returnType.getMethodAnnotations();
        String cryptoType = getCryptoType(annotations);
        if (cryptoType == null) {
            annotations = returnType.getDeclaringClass().getAnnotations();
            cryptoType = getCryptoType(annotations);
        }
        return cryptoType;
    }

    private String getCryptoType(Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if (annotation instanceof Encrypt) {
                return StringUtils.isNotBlank(((Encrypt) annotation).value()) ? ((Encrypt) annotation).value() : ((Encrypt) annotation).type().name();
            }
            if (annotation instanceof AesEncrypt) {
                return EncrptyTypeEnum.AES.name();
            }
            if (annotation instanceof DesEncrypt) {
                return EncrptyTypeEnum.DES.name();
            }
            if (annotation instanceof RsaEncrypt) {
                return EncrptyTypeEnum.RSA.name();
            }
            if (annotation instanceof Base64Encrypt) {
                return EncrptyTypeEnum.BASE64.name();
            }
            if (annotation instanceof EncryptDecrypt) {
                EncryptDecrypt encryptDecrypt = (EncryptDecrypt) annotation;
                return encryptDecrypt.value();
            }
        }
        return null;
    }
}
